//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.rrd.hive.udf;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hive.ql.exec.Description;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector.Category;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.StringObjectInspector;
import org.apache.hadoop.io.Text;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser.Feature;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;

@Description(
        name = "json_tuple_plus",
        value = "_FUNC_(jsonStr, p1, p2, ..., pn) - like get_json_object, but it takes multiple names and return a tuple. All the input parameters and output column types are string.",
        extended = "Example:\n> select json_tuple_plus('{\"a\": 1, \"b\": {\"c\": [{\"d\": 2}, {\"d\": 3}]}}', \"a\", \"b.c.0.d\");"
)
public class JsonTuplePlusUDTF extends GenericUDTF {
    private static Log LOG = LogFactory.getLog(JsonTuplePlusUDTF.class.getName());
    private static final JsonFactory JSON_FACTORY = new JsonFactory();
    private static final ObjectMapper MAPPER;
    private static final JavaType MAP_TYPE;
    int numCols;
    String[] paths;
    private transient Text[] retCols;
    private transient Text[] cols;
    private transient Object[] nullCols;
    private transient ObjectInspector[] inputOIs;
    boolean pathParsed = false;
    boolean seenErrors = false;
    static Map<String, Object> jsonObjectCache;

    public JsonTuplePlusUDTF() {
    }

    public void close() throws HiveException {
    }

    public StructObjectInspector initialize(ObjectInspector[] args) throws UDFArgumentException {
        this.inputOIs = args;
        this.numCols = args.length - 1;
        if (this.numCols < 1) {
            throw new UDFArgumentException("json_tuple_plus() takes at least two arguments: the json string and a path expression");
        } else {
            int i;
            for (i = 0; i < args.length; ++i) {
                if (args[i].getCategory() != Category.PRIMITIVE || !args[i].getTypeName().equals("string")) {
                    throw new UDFArgumentException("json_tuple_plus()'s arguments have to be string type");
                }
            }

            this.seenErrors = false;
            this.pathParsed = false;
            this.paths = new String[this.numCols];
            this.cols = new Text[this.numCols];
            this.retCols = new Text[this.numCols];
            this.nullCols = new Object[this.numCols];

            for (i = 0; i < this.numCols; ++i) {
                this.cols[i] = new Text();
                this.retCols[i] = this.cols[i];
                this.nullCols[i] = null;
            }

            ArrayList<String> fieldNames = new ArrayList(this.numCols);
            ArrayList<ObjectInspector> fieldOIs = new ArrayList(this.numCols);

            for (i = 0; i < this.numCols; ++i) {
                fieldNames.add("c" + i);
                fieldOIs.add(PrimitiveObjectInspectorFactory.writableStringObjectInspector);
            }

            return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
        }
    }

    public void process(Object[] o) throws HiveException {
        if (o[0] == null) {
            this.forward(this.nullCols);
        } else {
            if (!this.pathParsed) {
                for (int i = 0; i < this.numCols; ++i) {
                    this.paths[i] = ((StringObjectInspector) this.inputOIs[i + 1]).getPrimitiveJavaObject(o[i + 1]);
                }

                this.pathParsed = true;
            }

            String jsonStr = ((StringObjectInspector) this.inputOIs[0]).getPrimitiveJavaObject(o[0]);
            if (jsonStr == null) {
                this.forward(this.nullCols);
            } else {
                try {
                    Object jsonObj = jsonObjectCache.get(jsonStr);
                    if (jsonObj == null) {
                        try {
                            jsonObj = MAPPER.readValue(jsonStr, MAP_TYPE);
                        } catch (Exception var6) {
                            this.reportInvalidJson(jsonStr);
                            this.forward(this.nullCols);
                            return;
                        }

                        jsonObjectCache.put(jsonStr, jsonObj);
                    }

                    if (!(jsonObj instanceof Map)) {
                        this.reportInvalidJson(jsonStr);
                        this.forward(this.nullCols);
                    } else {
                        for (int i = 0; i < this.numCols; ++i) {
                            if (this.retCols[i] == null) {
                                this.retCols[i] = this.cols[i];
                            }
                            String[] jsonKeys;
                            if (this.paths[i].contains(".")) {
                                jsonKeys = this.paths[i].split("\\.");
                            } else {
                                jsonKeys = new String[]{this.paths[i]};
                            }
                            Object extractObject = ((Map) jsonObj).get(jsonKeys[0]);
                            for (int j = 1; j < jsonKeys.length; j++) {
                                if (!(extractObject instanceof Map) && !(extractObject instanceof List)) {
                                    extractObject = null;
                                    break;
                                }
                                if (extractObject instanceof Map) {
                                    extractObject = ((Map) extractObject).get(jsonKeys[j]);
                                } else if (extractObject instanceof List) {
                                    extractObject = ((List) extractObject).get(Integer.parseInt(jsonKeys[j]));
                                }
                            }

                            if (!(extractObject instanceof Map) && !(extractObject instanceof List)) {
                                if (extractObject != null) {
                                    this.retCols[i].set(extractObject.toString());
                                } else {
                                    this.retCols[i] = null;
                                }
                            } else {
                                this.retCols[i].set(MAPPER.writeValueAsString(extractObject));
                            }
                        }

                        this.forward(this.retCols);
                    }
                } catch (Throwable var7) {
                    var7.printStackTrace();
                    LOG.error("JSON parsing/evaluation exception" + var7);
                    this.forward(this.nullCols);
                }
            }
        }
    }

    public String toString() {
        return "json_tuple_plus";
    }

    private void reportInvalidJson(String jsonStr) {
        if (!this.seenErrors) {
            LOG.error("The input is not a valid JSON string: " + jsonStr + ". Skipping such error messages in the future.");
            this.seenErrors = true;
        }

    }

    static {
        JSON_FACTORY.enable(Feature.ALLOW_UNQUOTED_CONTROL_CHARS);
        MAPPER = new ObjectMapper(JSON_FACTORY);
        MAP_TYPE = TypeFactory.fromClass(Map.class);
        jsonObjectCache = new JsonTuplePlusUDTF.HashCache();
    }

    static class HashCache<K, V> extends LinkedHashMap<K, V> {
        private static final int CACHE_SIZE = 16;
        private static final int INIT_SIZE = 32;
        private static final float LOAD_FACTOR = 0.6F;
        private static final long serialVersionUID = 1L;

        HashCache() {
            super(32, 0.6F);
        }

        protected boolean removeEldestEntry(Entry<K, V> eldest) {
            return this.size() > 16;
        }
    }
}
